package hydrap2p.download;

import hydrap2p.helper.ServiceConnector;
import hydrap2p.logging.Level;
import hydrap2p.logging.Logging;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;
import java.util.TreeMap;

import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

/**
 * Helper class to store Download objects to file.
 * 
 * @author Phil
 */
public class DownloaderPersistence {

	// Class string name, doubling as logging marker
	private static final Marker TAG = MarkerFactory.getDetachedMarker("DownloaderPersistence");

	// Console logger - used before connections established
	private static final Logger consoleLogger = LoggerFactory.getLogger(TAG.getName());
	
	/**
	 * Save all downloads.
	 * 
	 * @param downloads the downloads
	 * @param file the file
	 * @return true, if successful
	 */
	public static boolean saveAllDownloads(Map<Long, Download> downloads, File file) {
		if(downloads != null) {
			return saveObject(downloads, file);
		} else {
			if(file.exists()) {
				file.delete();
			}
		}
		
		return false;
	}
	
	/**
	 * Load all downloads.
	 * 
	 * @param file the file
	 * @return the map
	 */
	public static Map<Long, Download> loadAllDownloads(File file) {
		Map<Long, Download> retList = null;
		
		try {
			retList = (Map<Long, Download>)loadObject(file);
			
			if(retList == null)
				retList = new TreeMap<Long, Download>();
		} catch(ClassCastException e) {
			e.printStackTrace();
		}
		
		return retList;
	}
	
	/**
	 * Save object.
	 * 
	 * @param obj the obj
	 * @param file the file
	 * @return true, if successful
	 */
	private static boolean saveObject(Object obj, File file) {
		try {
			if(file.isDirectory())
				return false;
			
			if(file.exists())
				file.delete();
		
			FileOutputStream fileOut = null;
			ObjectOutputStream objOut = null;
			
			try {
				fileOut = new FileOutputStream(file);
				objOut = new ObjectOutputStream(fileOut);
			} catch(FileNotFoundException e) {
				e.printStackTrace();
				return false;
			}
			
			objOut.writeObject(obj);
			
			objOut.flush();
			objOut.close();
			
			fileOut.close();
		} catch(IOException e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}
	
	/**
	 * Load object.
	 * 
	 * @param file the file
	 * @return the object
	 */
	private static Object loadObject(File file) {
		Object retObj = null;
		try {
			if(!file.exists())
				return retObj;
		
			FileInputStream fileIn = null;
			ObjectInputStream objIn = null;
			
			try {
				fileIn = new FileInputStream(file);
				objIn = new ObjectInputStream(fileIn);
			} catch(FileNotFoundException e) {
				e.printStackTrace();
				return retObj;
			} catch(EOFException e) {
				e.printStackTrace();
				log(Level.ERROR, "Failed to read download state!");
				file.delete();

				return retObj;
			}
			
			retObj = objIn.readObject();
		} catch(IOException e) {
			e.printStackTrace();
			log(Level.ERROR, "Failed to read download state!");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			log(Level.ERROR, "Failed to read download state!");
		} catch (ClassCastException e) {
			e.printStackTrace();
			log(Level.ERROR, "Failed to read download state!");
		} finally {
			file.delete();
		}
		
		return retObj;
	}
	
	/**
	 * Log.
	 * 
	 * @param severity the severity
	 * @param logMessage the log message
	 */
	public static void log(Level severity, String logMessage) {
		Logging.Client logging = ServiceConnector.getLogger();
		
		//Log to Logging module if possible, and fall back to console.
		try {
			logging.log(severity, TAG.toString(), logMessage);
		} catch(TException e) {
			logToSystemError(severity, logMessage);
		} catch(NullPointerException e) {
			logToSystemError(severity, logMessage);
		}
	}
	
	/**
	 * Log to system error.
	 * 
	 * @param severity the severity
	 * @param logMessage the log message
	 */
	private static void logToSystemError(Level severity, String logMessage) {
		//Log to system error.  If they can't be arsed to use a Logging constant as severity, the message gets logged at trace level
		switch(severity) {
		case VERBOSE:
			consoleLogger.trace(TAG, logMessage);
			break;
		case DEBUG:
			consoleLogger.debug(TAG, logMessage);
			break;
		case INFO:
			consoleLogger.info(TAG, logMessage);
			break;
		case MISHAP:
			consoleLogger.warn(TAG, logMessage);
			break;
		case ERROR:
			consoleLogger.error(TAG, logMessage);
			break;
		default:
			consoleLogger.trace(TAG, logMessage);
		}
	}
}
